/**
 * Copyright 2001-2008, Focus Technologies LLC. All rights reserved.
 * 
 * @author James Pearson (jpearson@motio.com)
 */
package com.mrfechetto.utils;

import java.awt.event.ActionListener;
import java.awt.event.ComponentListener;
import java.awt.event.FocusListener;
import java.awt.event.HierarchyListener;
import java.awt.event.ItemListener;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.WindowListener;
import java.awt.event.WindowStateListener;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;

import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentListener;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.MenuListener;
import javax.swing.event.TableModelListener;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionListener;

/**
 * This class is to be used to create swing/awt listener objects dynamically and bind 
 * them to target handler methods. 
 * 
 * At current each of the public methods of this class corresponds to a specific
 * method in various swing/awt listener objects. For example the 
 * <code>ActionListener actionPerformed(Object target, String targetMethodName)</code>
 * will create an ActionListener that listens for the actionPerformed event. The
 * method referred to by String targetMethodName on Object target, should accept the
 * single argument of type ActionEvent as corresponds to actionPerformed method in
 * the ActionListener interface. Also the target method should be public.
 * 
 * If you need to create another type of event which
 * is not publicly implemented then you should implement a public method like the
 * <code>public static ActionListener actionPerformed(Object target, String targetMethodName)</code>
 * 
 * Please refrain from making any of the private methods here into public ones or you will
 * be publicly humiliated.
 * 
 * @author James Pearson (jpearson@motio.com)
 *
 */
public abstract class ListenerFactory
{
   /**
    * This method is used to create a listener of type ActionListener. The
    * <code>targetMethodName</code> on <code>target</code> should exist and
    *  should also accept only one argument of type ActionEvent such that it
    *  matches the one in the defined in the ActionListener interface.
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return ActionListener
    */
   public static ActionListener actionPerformed(Object target, String targetMethodName)
   {
      return (ActionListener) create(ActionListener.class,"actionPerformed",target,targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static TreeExpansionListener treeCollapsed(Object target, String targetMethodName)
   {
      return (TreeExpansionListener) create(TreeExpansionListener.class, "treeCollapsed" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static TreeExpansionListener treeExpanded(Object target, String targetMethodName)
   {
      return (TreeExpansionListener) create(TreeExpansionListener.class, "treeExpanded" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static MouseListener mouseClicked(Object target, String targetMethodName)
   {
      return (MouseListener) create(MouseListener.class, "mouseClicked" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static MouseListener mouseEntered(Object target, String targetMethodName)
   {
      return (MouseListener) create(MouseListener.class, "mouseEntered" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static MouseListener mouseExited(Object target, String targetMethodName)
   {
      return (MouseListener) create(MouseListener.class, "mouseExited" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static MouseListener mousePressed(Object target, String targetMethodName)
   {
      return (MouseListener) create(MouseListener.class, "mousePressed" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static MouseListener mouseReleased(Object target, String targetMethodName)
   {
      return (MouseListener) create(MouseListener.class, "mouseReleased" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static ChangeListener stateChanged(Object target, String targetMethodName)
   {
      return (ChangeListener) create(ChangeListener.class, "stateChanged" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static ListSelectionListener valueChanged(Object target, String targetMethodName)
   {
      return (ListSelectionListener) create(ListSelectionListener.class, "valueChanged" , target , targetMethodName);
   }
   
   public static TreeSelectionListener treeValueChanged(Object target, String targetMethodName)
   {
      return (TreeSelectionListener) create(TreeSelectionListener.class, "valueChanged" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static WindowListener windowActivated(Object target, String targetMethodName)
   {
      return (WindowListener) create(WindowListener.class, "windowActivated" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static WindowListener windowClosed(Object target, String targetMethodName)
   {
      return (WindowListener) create(WindowListener.class, "windowClosed" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static WindowListener windowClosing(Object target, String targetMethodName)
   {
      return (WindowListener) create(WindowListener.class, "windowClosing" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static WindowListener windowDeactivated(Object target, String targetMethodName)
   {
      return (WindowListener) create(WindowListener.class, "windowDeactivated" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static WindowListener windowDeiconified(Object target, String targetMethodName)
   {
      return (WindowListener) create(WindowListener.class, "windowDeiconified" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static WindowListener windowIconified(Object target, String targetMethodName)
   {
      return (WindowListener) create(WindowListener.class, "windowIconified" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static WindowListener windowOpened(Object target, String targetMethodName)
   {
      return (WindowListener) create(WindowListener.class, "windowOpened" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author Ethan Bowen (ebowen@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static WindowStateListener windowStateChanged(Object target, String targetMethodName)
   {
      return (WindowStateListener) create(WindowStateListener.class, "windowStateChanged" , target , targetMethodName);
   }
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static HyperlinkListener hyperlinkUpdate(Object target, String targetMethodName)
   {
      return (HyperlinkListener) create(HyperlinkListener.class, "hyperlinkUpdate" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static FocusListener focusGained(Object target, String targetMethodName)
   {
      return (FocusListener) create(FocusListener.class, "focusGained" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static FocusListener focusLost(Object target, String targetMethodName)
   {
      return (FocusListener) create(FocusListener.class, "focusLost" , target , targetMethodName);
   }
   
   /**
    * 
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static ItemListener itemStateChanged(Object target, String targetMethodName)
   {
      return (ItemListener) create(ItemListener.class, "itemStateChanged" , target , targetMethodName);
   }

   /**
    *
    *
    * @author Matt Meza (mmeza@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static KeyListener keyTyped(Object target, String targetMethodName)
   {
      return (KeyListener) create(KeyListener.class, "keyTyped" , target , targetMethodName);
   }
   
   /**
    *
    *
    * @author Matt Meza (mmeza@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static KeyListener keyPressed(Object target, String targetMethodName)
   {
      return (KeyListener) create(KeyListener.class, "keyPressed" , target , targetMethodName);
   }
   
   public static DocumentListener changedUpdate(Object target, String targetMethodName)
   {
      return (DocumentListener) create(DocumentListener.class, "changedUpdate" , target , targetMethodName);
   }
   
   public static DocumentListener insertUpdate(Object target, String targetMethodName)
   {
      return (DocumentListener) create(DocumentListener.class, "insertUpdate" , target , targetMethodName);
   }
   
   public static DocumentListener removeUpdate(Object target, String targetMethodName)
   {
      return (DocumentListener) create(DocumentListener.class, "removeUpdate" , target , targetMethodName);
   }

   /**
    *
    *
    * @author Matt Meza (mmeza@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static KeyListener keyReleased(Object target, String targetMethodName)
   {
      return (KeyListener) create(KeyListener.class, "keyReleased" , target , targetMethodName);
   }
   
   public static TableModelListener tableChanged(Object target, String targetMethodName)
   {
      return (TableModelListener) create(TableModelListener.class, "tableChanged", target, targetMethodName);
   }
   
   public static MenuListener menuSelected(Object target, String targetMethodName)
   {
      return (MenuListener) create(MenuListener.class, "menuSelected", target, targetMethodName);
   }
   
   public static MenuListener menuDeselected(Object target, String targetMethodName)
   {
      return (MenuListener) create(MenuListener.class, "menuDeselected", target, targetMethodName);
   }
   
   public static MenuListener menuCanceled(Object target, String targetMethodName)
   {
      return (MenuListener) create(MenuListener.class, "menuCanceled", target, targetMethodName);
   }
   
   /**
    * This listener is used to listen for changes in the hierarchy of a swing component.
    * 
    * @author James Pearson (jpearson@motio.com)
    * @param target
    * @param targetMethodName
    * @return
    */
   public static HierarchyListener hierarchyChanged(Object target, String targetMethodName)
   {
      return (HierarchyListener) create(HierarchyListener.class, "hiearchyChanged", target, targetMethodName);
   }

   public static ComponentListener componentHidden(Object target, String targetMethodName)
   {
      return (ComponentListener) create(ComponentListener.class, "componentHidden", target, targetMethodName);
   }
   
   public static ComponentListener componentShown(Object target, String targetMethodName)
   {
      return (ComponentListener) create(ComponentListener.class, "componentShown", target, targetMethodName);
   }
   
   
   /**
    * If your thinking of making this public ... dont!
    * 
    * A convenient version of <code>create(listenerMethod, targetObject, targetMethod)</code>.
    * This version looks up the listener and target Methods, so you don't have to.
    * @author James Pearson (jpearson@motio.com)
    */
   private static Object create(
         Class<?> listenerInterface,
         String listenerMethodName,
         Object target,
         String targetMethodName)
   {
      Method listenerMethod = getListenerMethod(listenerInterface, listenerMethodName);
      Method targetMethod = 
         getTargetMethod(target, targetMethodName, listenerMethod.getParameterTypes());

      return create(listenerMethod, target, targetMethod);
   }


   /**
    * Return an instance of a class that implements the interface that contains 
    * the declaration for <code>listenerMethod</code>.  In this new class,
    * <code>listenerMethod</code> will apply <code>target.targetMethod</code>
    * to the incoming Event.
    * @author James Pearson (jpearson@motio.com)
    */
   private static Object create(final Method listenerMethod, final Object target, final Method targetMethod)
   {
      /*
       * The implementation of the create method uses the Dynamic Proxy API
       * introduced in JDK 1.3.
       *
       * Create an instance of the DefaultInvoker and override the invoke 
       * method to handle the invoking the targetMethod on the target.
       */
      InvocationHandler handler = new DefaultInvoker() {

         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {



            // Send all methods execept for the targetMethod to
            // the superclass for handling.

            if (listenerMethod.equals(method)) {

               return targetMethod.invoke(target, args);

            } else {
               return super.invoke(proxy, method, args);
            }

         }

      };



      Class<?> cls = listenerMethod.getDeclaringClass();

      ClassLoader cl = cls.getClassLoader();



      return Proxy.newProxyInstance(cl, new Class[]{cls}, handler);
   }

   /** 
    * Implementation of the InvocationHandler which handles the basic
    * object methods.
    */
   private static class DefaultInvoker implements InvocationHandler  {

      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

         if (method.getDeclaringClass() == Object.class)  {
            String methodName = method.getName();
            if (methodName.equals("hashCode"))  {
               return proxyHashCode(proxy);   
            } else if (methodName.equals("equals")) {
               return proxyEquals(proxy, args[0]);
            } else if (methodName.equals("toString")) {
               return proxyToString(proxy);
            }
         }

         // Although listener methods are supposed to be void, we 

         // allow for any return type here and produce null/0/false

         // as appropriate.

         return nullValueOf(method.getReturnType());
      }


      protected Integer proxyHashCode(Object proxy)  {
         return new Integer(System.identityHashCode(proxy));
      }

      protected Boolean proxyEquals(Object proxy, Object other)  {
         return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
      }

      protected String proxyToString(Object proxy)  {
         return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());
      }

      private final static Character char_0 = new Character((char)0);
      private final static Byte byte_0 = new Byte((byte)0);

      private final static Object nullValueOf(Class<?> rt) {
         if (!rt.isPrimitive()) {
            return null;
         }
         else if (rt == void.class) {
            return null;
         }
         else if (rt == boolean.class) {
            return Boolean.FALSE;
         }
         else if (rt == char.class) {
            return char_0;
         }
         else {
            // this will convert to any other kind of number
            return byte_0;
         }
      }

   }

   /* Helper methods for "EZ" version of create(): */

   private static Method getListenerMethod(Class<?> listenerInterface,
         String listenerMethodName) {
      // given the arguments to create(), find out which listener is desired:
      Method[] m = listenerInterface.getMethods();
      Method result = null;
      for (int i = 0; i < m.length; i++) {
         if (!listenerMethodName.equals(m[i].getName()))  continue;
         if (result != null) {
            throw new RuntimeException("ambiguous method: "+m[i]+" vs. "+result);
         }
         result = m[i];
      }
      if (result == null) {
         throw new RuntimeException("no such method "+listenerMethodName+" in "+listenerInterface);
      }
      return result;
   }

   private static Method getTargetMethod(
         Object target,
         String targetMethodName,
         Class<?>[] parameterTypes) {
      Method[] m = target.getClass().getMethods();
      Method result = null;
      eachMethod:
         for (int i = 0; i < m.length; i++) {
            if (!targetMethodName.equals(m[i].getName()))  continue eachMethod;
            Class<?>[] p = m[i].getParameterTypes();
            if (p.length != parameterTypes.length)  continue eachMethod;
            for (int j = 0; j < p.length; j++) {
               if (!p[j].isAssignableFrom(parameterTypes[j]))  continue eachMethod;
            }
            if (result != null) {
               throw new RuntimeException("ambiguous method: "+m[i]+" vs. "+result);
            }
            result = m[i];
         }
      if (result == null) {
         throw new RuntimeException("no such method "+targetMethodName+" in "+target.getClass());
      }

      Method publicResult = raiseToPublicClass(result);
      if (publicResult != null) result = publicResult;

      return result;
   }

   private static Method raiseToPublicClass(Method m) {
      Class<?> c = m.getDeclaringClass();
      if ( Modifier.isPublic(m.getModifiers()) &&
            Modifier.isPublic(c.getModifiers()) )
         return m; // yes!
      // search for a public version which m overrides
      Class<?> sc = c.getSuperclass();
      if (sc != null) {
         Method sm = raiseToPublicClass(m, sc);
         if (sm != null)  return sm;
      }
      Class<?>[] ints = c.getInterfaces();
      for (int i = 0; i < ints.length; i++) {
         Method im = raiseToPublicClass(m, ints[i]);
         if (im != null)  return im;
      }
      // no public version of m here
      return null;
   }


   private static Method raiseToPublicClass(Method m, Class<?> c) {
      try {
         Method sm = c.getMethod(m.getName(), m.getParameterTypes());
         return raiseToPublicClass(sm);
      }
      catch (NoSuchMethodException ee) {
         return null;
      }
   }



}
